/*Set tab stop to 1 */
/*-----------------------------------------------------------------------------
 * File Name ..... main.c
 *
 * Purpose ....... Main program for parallel heap
 *
 * Functions ..... main
 *
 * Date Written .. April 10, 1993
 *
 * Modification History
 *    Author            Date                          Purpose
 *    ------            ----                          -------
 * Sagar Sawant       April 28, 1993         The original version of main.c
 * Dinesh Agarwal     July  12, 2009         Ported the earlier version using PThreads.
 *---------------------------------------------------------------------------*/


/*-------------------------- Include Files ----------------------------------*/
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/times.h>
#include <sys/param.h>
#include "pardef.h"
#include "parheap.h"
#include "timer.h"
#include <pthread.h>
#include <sys/time.h>
/*-------------------------- # defines ---------------------------------------*/
#define SGIPROC 30

/*-------------------------- Extern Functions -------------------------------*/
extern void GetSubstituteItems(int num,PARHEAP *Items);
extern void *PerformInsertDelete(void *threadargs);
extern void *MyMalloc(int);
extern void *MyCalloc(int);
extern void CreateParHeap(int InitialNoOfItems);
extern void DeAllocateParHeap(int InitialNoOfItems);
extern void PrintParHeap(FILE *outfile);
extern void *Prefetch(void *pfdata);
/*-------------------------- Extern Variables -------------------------------*/
extern int r;                          /* No. of items in one node */
extern int LastLevel1;
extern PARHEAP ***ParHeap;
Prefetch_data *pfd; /* Structure to  handle the data for prefetching as well as for sleeping and waking the thread*/
/* Different Barriers created, based on number of threads needed to touch the barrier before it gets revoked */
pthread_barrier_t Maintenance_Barrier;
pthread_barrier_t WorkerWithP0_Barrier;
pthread_barrier_t Sync_Barrier;
pthread_barrier_t Barrier_PMinus1;
pthread_barrier_t Worker_Barrier; /* for ParGetInsertitems' condition Nop - M */
/*--------internals--------*/
int MAX_THINK_TIME;
int thinking;
PARHEAP *DeletedItems;
PARHEAP *NewItems;      /* New insert items */
int M, count;
long magicTime;

/*----------------------------------------------------------------------------
 * Function ........... main
 *
 * Purpose ............ Driver function for parallel heap
 *
 * Arguments Passed ... argc - Should be equal to 8, otherwise error
 *                      argv - 1. No. of processes to be executed in parallel
 *                             2. No. of heap items
 *                             3. No. of insert delete cycles
 *                             4. Random number k used to get priority of new
 *                                insert items.
 *                             5. No. of items to be deleted in each cycle
 *                             6. No. of items to be inserted in each cycle
 *                             7. Name of output file
 *
 * Return Value ....... None
 *
 * Globals Modified ... r,
 *---------------------------------------------------------------------------*/
int main(argc, argv)
int argc;
char *argv[];
{
    int NoOfProcesses , NoOfInsertDeleteCycles, RandomK;
    int InitialNoOfItems;
    int ToBeDeleted, ToBeInserted;
    double StartTime, EndTime;
    void * tstatus;
    int tmp=0;
    int num;
    double difference;
    pthread_attr_t *Pthread_attr;/* To set affinity for each thread */
    /* Thread_Data is a structure to pass arguments during Thread Invocation */
    THREAD_DATA *t_data_array;
    /* A thread pointer's array */
    pthread_t *threads;/* Worker threads */
#ifdef PF
    pthread_t *PFthreads; /* Thread for prefetching */
    void * pfstatus;
#endif
    if (argc != 9)
    {
        fprintf(stderr, "Usage: %s <No of threads> <r> <Initial No of items> <No of cycles> \n <think> <No of items to be deleted> <No of items to be inserted> <M>\n", argv[0]);
        exit(0);
    }
    srand(200);

    NoOfProcesses = atoi(argv[1]);
    r = atoi(argv[2]);
    InitialNoOfItems = atol(argv[3]);
    //fprintf(stdout,"%ld\n",InitialNoOfItems);
    NoOfInsertDeleteCycles = atoi(argv[4]);
    MAX_THINK_TIME= atoi(argv[5]);
    RandomK = 50;
    ToBeDeleted = atoi(argv[6]);
    ToBeInserted = atoi(argv[7]);
    M = atoi(argv[8]);

    if ( (NoOfProcesses > SGIPROC) ||
            (ToBeDeleted > r) || (ToBeInserted > 2*r) )
    {
        if (NoOfProcesses > SGIPROC)
            fprintf(stderr, "No. of threads must be less than %d\n", SGIPROC);
        if (ToBeDeleted > r)
            fprintf(stderr,
                    "No of items to be deleted should be less or equal to %d\n",r);
        if (ToBeInserted > 2*r)
            fprintf(stderr,
                    "No of items to be inserted should be less or equal to %d\n", 2*r);
        exit(0);
    }

    //CreateParHeap(InitialNoOfItems + abs(ToBeInserted - ToBeDeleted) * NoOfInsertDeleteCycles);
    CreateParHeap(2 * InitialNoOfItems); 
    if (pthread_barrier_init(&Maintenance_Barrier, NULL, M))
    {
        fprintf(stderr,"Could not create  Maintenance barrier1\n");
        return -1;
    }

    if (NoOfProcesses > 1 )
    {
        if (pthread_barrier_init(&Barrier_PMinus1, NULL, NoOfProcesses))
        {
            fprintf(stderr,"Could not create  barrier2p\n");
            return -1;
        }
    }
    if (pthread_barrier_init(&WorkerWithP0_Barrier, NULL, NoOfProcesses-M+1))
    {
        fprintf(stderr,"Could not create  barrier2\n");
        return -1;
    }
    if (pthread_barrier_init(&Sync_Barrier, NULL, NoOfProcesses))
    {
        fprintf(stderr,"Could not create Sync_barrier\n");
        return -1;
    }
    num = NoOfProcesses - M;
    if (num == 0 )  num = NoOfProcesses;
    if (pthread_barrier_init(&Worker_Barrier, NULL, num))
    {
        fprintf(stderr,"Could not create barrier3\n");
        return -1;
    }

    t_data_array = (THREAD_DATA *) MyMalloc(NoOfProcesses * sizeof(THREAD_DATA));
    threads = (pthread_t *) MyMalloc(NoOfProcesses * sizeof(pthread_t));
    Pthread_attr = (pthread_attr_t *) MyMalloc(sizeof(pthread_attr_t) * NoOfProcesses);
    pthread_attr_init(Pthread_attr);
    pthread_attr_setdetachstate(Pthread_attr, PTHREAD_CREATE_JOINABLE);
#ifdef PF 
    pfd       = (Prefetch_data *) MyMalloc(NoOfProcesses * sizeof(Prefetch_data));
    PFthreads = (pthread_t *) MyMalloc(NoOfProcesses * sizeof(pthread_t));
#endif

    DeletedItems = (PARHEAP *) MyMalloc( 3 * r * sizeof(PARHEAP));
    NewItems = (PARHEAP *) MyMalloc( 2 * r * sizeof(PARHEAP));

    StartTime = my_difftime();

    for (tmp=0; tmp<NoOfProcesses; tmp++)
    {
        int rc;
        /* Wrapping the data into one single struct as PThreads Lib only supports
        one single argument to be passed to the function called by thread */
        t_data_array[tmp].NoOfInsertDeleteCycles = NoOfInsertDeleteCycles;
        t_data_array[tmp].NoOfProcesses=NoOfProcesses;
        t_data_array[tmp].RandomK=RandomK;
        t_data_array[tmp].ToBeDeleted=ToBeDeleted;
        t_data_array[tmp].ToBeInserted=ToBeInserted;
        t_data_array[tmp].taskid = tmp;
        rc = 0;
#ifdef PF
        pthread_mutex_init(&(pfd[tmp].mutex),NULL);
        pthread_cond_init(&(pfd[tmp].cond),NULL);
        /* Wrpping data for prefetching. Initially these threads will sleep (spinning) */
        pfd[tmp].spin = OK_SPIN;
        pfd[tmp].tid = tmp;
        memset((void *)pfd[tmp].temp, 0, sizeof(pfd[tmp].temp));
        /* Create Prefetching Threads */
        if (tmp<M)
        {
            rc = pthread_create(&PFthreads[tmp], Pthread_attr, &Prefetch, (void *) &(pfd[tmp]));
            if (rc)
            {
                fprintf(stdout,"ERROR; return code from pthread_create() is %d\n", rc);
                exit(-1);
            }
        }
#endif
        /* Create Worker Threads */
        rc = pthread_create(&threads[tmp], Pthread_attr, &PerformInsertDelete, (void *) &t_data_array[tmp]);
        if (rc)
        {
            fprintf(stdout,"ERROR; return code from pthread_create() is %d\n", rc);
            exit(-1);
        }
    }
#ifdef PF
        for (tmp=0; tmp<M; tmp++)
        {
            int rc = pthread_join(PFthreads[tmp], &pfstatus);
            if (rc)
            {
                fprintf(stdout,"ERROR; return code from pfthread_join() is %d\n", rc);
                exit(-1);
            }
        }
#endif
    for (tmp=0; tmp<NoOfProcesses; tmp++)
    {
        int rc = pthread_join(threads[tmp], &tstatus);
        if (rc)
        {
            fprintf(stdout,"ERROR; return code from pthread_join() is %d\n", rc);
            exit(-1);
        }
    }

    EndTime = my_difftime();
    difference = EndTime - StartTime;
    pthread_attr_destroy(Pthread_attr);
    free(ParHeap);
    free(Pthread_attr);
    free(t_data_array);
    free(threads);
#ifdef PF
    free(pfd);
    free(PFthreads);
#endif



    fprintf (stdout,"%f\t", difference);
    return EXIT_SUCCESS;
}

